
package com.whosly.mysql.protocol.packet.impl;

import com.whosly.mysql.protocol.MysqlErrorCode;
import com.whosly.mysql.protocol.jdbc.MySQLMessage;
import com.whosly.mysql.protocol.packet.jdbc.AbstractMySQLPacket;
import com.whosly.mysql.protocol.packet.util.NettyBufferUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;

/**
 * Error Packet
 *
 * <pre>
 *  VERSION 4.1
 *  Bytes                       Name
 *  -----                       ----
 *  1                           field_count, always = 0xff
 *  2                           errno
 *  1                           (sqlstate marker), always '#'
 *  5                           sqlstate (5 characters)
 *  n                           message
 *
 *  field_count:       Always 0xff (255 decimal).
 *
 *  errno:             The possible values are listed in the manual, and in
 *                     the MySQL source code file /include/mysqld_error.h.
 *
 *  sqlstate marker:   This is always '#'. It is necessary for distinguishing
 *                     version-4.1 messages.
 *
 *  sqlstate:          The server translates errno values to sqlstate values
 *                     with a function named mysql_errno_to_sqlstate(). The
 *                     possible values are listed in the manual, and in the
 *                     MySQL source code file /include/sql_state.h.
 *
 *  message:           The error message is a string which ends at the end of
 *                     the packet, that is, its length can be determined from
 *                     the packet header. The MySQL client (in the my_net_read()
 *                     function) always adds '/0' to a packet, so the message
 *                     may appear to be a Null-Terminated String.
 *                     Expect the message to be between 0 and 512 bytes long.
 * </pre>
 *
 * @author fengyang(yueny09@163.com)
 * @date 2023年06月25日 下午8:34
 */
public class ErrorPacket extends AbstractMySQLPacket
{

    private static final byte SQLSTATE_MARKER = (byte) '#';
    private static final byte[] DEFAULT_SQLSTATE = "HY000".getBytes();

    /**
     * ERRPacket error message前的字符长度
     */
    private static final int LENGTH_BEFORE_MSG = 9;// 1 + 2 + 1 + 5


    /**
     * 包头标识, 0xff 代表这是一个 ERROR 包
     */
    public byte resultPackType = PACKET_ERROR;
    /**
     * 错误代码
     */
    public int errno;
    /**
     * 标识位. SQL执行状态标识位，用’#’进行标识. 固定. 无需解析
     */
    public byte mark = SQLSTATE_MARKER;
    /**
     * 执行状态,	SQL的具体执行状态
     * 长度 5
     */
    public byte[] sqlstate = DEFAULT_SQLSTATE;
    /**
     * 错误信息, 具体的错误信息
     *
     * 错误消息字符串到达消息尾时结束，长度可以由消息头中的长度值计算得出。消息长度为0-512字节。
     */
    public byte[] message;

    public ErrorPacket() {
        //.
    }

    public ErrorPacket(int packId, MysqlErrorCode errorCode, final byte[] errorMessageArgument) {
        setPackId((byte) packId);
        this.errno = errorCode.getErrorNo();
        this.sqlstate = errorCode.getSqlState().getBytes();

        this.message = errorMessageArgument;
    }

    public ErrorPacket(int packId, int errno, String sqlState, final byte[] errorMessageArgument) {
        setPackId((byte) packId);
        this.errno = errno;
        this.sqlstate = sqlState.getBytes();

        this.message = errorMessageArgument;
    }

    @Override
    public void read(byte[] bodyBytes) {
        MySQLMessage mm = new MySQLMessage(bodyBytes);

        readBody(mm);
    }

    private void readBody(MySQLMessage mm) {
        resultPackType = mm.read();
        errno = mm.readUB2();

        if (mm.hasRemaining() && (mm.read(mm.position()) == SQLSTATE_MARKER)) {
            mm.read();
            sqlstate = mm.readBytes(5);
        }
        int messageLength = getPacketLength() - LENGTH_BEFORE_MSG; // 1 + 2 + 1 + 5
        if (messageLength >= 0) {
            message = mm.readBytes(messageLength);
        }
    }

    public void write(ChannelHandlerContext ctx) {
        int size = calcPacketSize();
        // default 256 , no need to check and auto expand
        ByteBuf buffer = ctx.alloc().buffer(size);

        writeBuf(buffer);

        ctx.writeAndFlush(buffer);
    }

    @Override
    public ByteBuf writeBuf(ByteBuf byteBuf)  {
        NettyBufferUtil.writeUB3(byteBuf, byteBuf.capacity());
        byteBuf.writeByte(getPackId());

        writeBody(byteBuf);

        return byteBuf;
    }

    @Override
    public ByteBuf writeBuf() {
        ByteBuf buffer = Unpooled.buffer(calcPacketSize());

        return writeBuf(buffer);
    }

    private void writeBody(ByteBuf buffer){
        buffer.writeByte(resultPackType);
        NettyBufferUtil.writeUB2(buffer, errno);
        buffer.writeByte(mark);
        buffer.writeBytes(sqlstate);
        if (message != null) {
            buffer.writeBytes(message);
        }
    }

    @Override
    public int calcPacketSize() {
        int size = LENGTH_BEFORE_MSG;// 1 + 2 + 1 + 5
        if (message != null) {
            size += message.length;
        }
        return size;
    }

    @Override
    public String getPacketInfo() {
        return "MySQL Error Packet";
    }

}